Generate API Spec จาก Swagger JSON

เนื่องจากมีงานที่ต้องทำแต่ API เพียวๆ แล้วเจ้าตัว Swagger ไม่ตอบโจทย์ที่ลูกค้าต้องการ เลยเป็นประเด็นให้เกิด Blog นี้
สำหรับใครที่ไม่รู้จักเจ้า Swagger ก็รองศึกษาดูจาก Swagger

Swagger เรียกๆ ง่ายๆ ก็คือตัวช่วยตัวนึงที่ช่วยให้ API ที่เราทำมีหน้าสำหรับทดสอบและอธิบายแทน Document โดยที่เราไม่ต้องเขียนเอง ช่วยงาน Developer ด้วยกันได้ดีมากๆ (ฟังดูก็ครบถ้วนนิ หึหึ)

เหตุผลหลักๆ ที่ไม่ตอบโจทย์ตามต้องการมีดังนี้

  1. เอกสารอยู่ในรูปแบบของ html เวลาเปิดดูแล้วเหมือนจะสะดวก แต่ถ้าเราต้องดู Schema ควบคู่ไปด้วยจะได้ Scroll mouse ไปๆ มาๆ
  2. เนื่องเอกสารที่ Swagger ทำให้สุดท้ายจะเป็นรูปแบบ html ทำให้เวลาแก้ไขเพิ่มเติมไม่สะดวก
  3. การพิมพ์อธิบาย Condition ต่างๆ ยาวๆ ทำได้ไม่สะดวก
  4. หลายๆ องค์กรยังคงต้องการเอกสารส่งงานที่ดูเป็นทางการอย่าง Word,Excel อยู่

ด้วยเหตุผลด้านบน(เจอมากับตัว) ทำให้ต้องมาทำเอกสารเหมือนเดิม แต่เราไม่อยากเสียเวลาต้องทำใหม่ทั้งหมดเพราะเรามีเจ้า Swagger ทำมาให้เยอะแล้วนิ เลยหาวิธี Convert จากเจ้า Swagger มาเป็น เอกสารอย่าง Word หรือ Excel ให้ได้ดีกว่า

หากท่านใดเจอเครื่องมือที่ดีอยู่แล้วหรือมีวิธีที่ดีกว่าวิธีที่ผมจะอธิบายด้านล่างนี้รบกวนแจ้งให้ทราบด้วยครับ เนื่องจากผมพยายามหาอยู่นานแต่ไม่พบเลยตัดสินใจ เขียนมันเองซะเลย

เจ้า Swagger เนี่ย นอกจากสร้างหน้า Web ให้เราใช้งานง่ายแล้ว แต่มันยังมีเป็นรูปแบบ JSON ให้เรานำไปใช้งานได้ด้วย มี Document อธิยายโครงสร้างชัดเจน ตัวนี้ละที่ช่วยเราได้

อธิบาย JSON Structure แบบสรุปคราวๆ ก่อน

Root ของ JSON จะเป็นดังนี้ สิ่งที่เราสนใจจริงๆ คือ paths และ definitions โดยที่
paths : เป็นรายการของ API และมี Parameter Request/Response บางส่วนที่ไม่ซับซ้อน
definitions : เป็น Schema หรือ Model ของ Parameter Request/Response แบบซับซ้อน

เข้าไปดูที่ paths สิ่งที่เราสนใจจะมีอยู่หลาย Parameter เหมือนกัน โดยส่วนที่ซับซ้อนจะเป็นส่วนของ schema เพราะจะเป็นส่วนที่อ้างถึง Schema ที่อยู่ใน definitions ทำให้เราต้องเขียนข้อมูลไปหา Schema ที่ต้องการในนั้นต่อ

เข้าไปดูที่ definitions จะเป็นรายการของ Schema ทั้งหมด โดยส่วนที่ซับซ้อนจะเป็นส่วนที่เรียกหา definitions เองอีกครั้ง ทำให้ในการเขียน Logic เราควรเขียนให้เป็น Recursive

ดู Document ที่อธิบาย JSON structure เพิ่มได้ที่ Swagger JSON Specification

เริ่มทำเครื่องมือ

ผมจะทำเครื่องมือที่ Convert Swagger JSON ให้ออกมาเป็น Excel API Spec แบบ Simple แต่มี Column ครบพร้อมให้นำไปเขียนหรือแก้ไขเพิ่มได้เลย โดยหน้าตา Excel ที่ได้จะเป็นดังนี้
มี Sheet index ที่รวมรายชื่อของ API ทั้งหมด และใส่สูตร Formula ให้ Click ไปที่ Sheet นั้นได้เลย

มี Column ครบตามที่ API Spec ควรจะมี บอกลำดับชั้นของ Parameter และมี Link Click กลับมา Sheet Index

หากเราเข้าใจ JSON Structure แล้วจะใช้เครื่องมืออะไรทำก็ได้ละ C#, VB, JAVA, Javascript ทางผู้เขียนขอเลือก Javascript ละกัน พอดีกำลังศึกษา Node.js หลักการที่ผมจะทำก็คือจะสร้างตัวแปรเก็บค่าทั้งชื่อ API, Method Type, Description, Request Parameter,Response Parameter ทั้งหมดเก็บไว้ก่อน แล้วค่อยนำไปเขียนสร้างเป็น Excel ออกมาทีเดียว ที่ทำแบบนี้เพื่อให้ง่ายต่อการนำไปใช้ตอนทำ Excel และ Code น่าจะดูสะอาดขึ้น

1. เริ่มแรกก็ npm install package ตัวช่วยก่อน ที่เลือกใช้มีหลักๆ มี 3 ตัว คือ

  1. excel4node : เพราะเราจะสร้างเป็น excel เลยต้องมีตัวช่วยสร้าง excel ให้เรา
  2. request : สำหรับ request json จาก URL Swagger เลย
  3. request-promise : ตัวช่วยทำ async ของ request
npm install excel4node request request-promise

2. เขียน request JSON จาก URL Swagger วิธีดูว่า URL JSON ของ Swagger ของเราคืออะไรดูได้จากหน้า Swagger เองเลยครับ ตามรูป

var rp = require('request-promise');
var swaggerJsonUrl = 'http://xxxxxx:xxxx/xxx/v2/api-docs?group=CustomerMaster';
var json;
var options = {
    uri: swaggerJsonUrl,    
    json: true // Automatically stringifies the body to JSON
};
 console.log('Swagger URL : ' + swaggerJsonUrl);
rp(options)
.then(function (parsedBody) {
    console.log('Get Url Response');
    json = parsedBody;
    console.log('Call generateExcel');
    generateExcel();
})
.catch(function (err) {
    console.log('Error : ' + err);
});

3. สร้าง Class Model สำหรับเก็บ API, Request และ Response

"use strict";
exports.__esModule = true;
var modelService = /** @class */ (function () {
    function modelService() {
        this.module = '';
        this.service = '';
        this.methodType = '';
        this.description = '';
        this.request = [];
        this.response = [];
    }
    return modelService;
}());
exports.modelService = modelService;
var modelRequest = /** @class */ (function () {
    function modelRequest() {
        this.name = '';
        this.paramType = '';
        this.type = '';
        this.lv = '';
        this.description = '';
        this.required = '';
        this.simpleValue = '';
        this.posibleValue = '';
    }
    return modelRequest;
}());
exports.modelRequest = modelRequest;
var modelResponse = /** @class */ (function () {
    function modelResponse() {
        this.name = '';
        this.paramType = '';
        this.type = '';
        this.lv = '';
        this.description = '';
        this.required = '';
        this.simpleValue = '';
        this.posibleValue = '';
    }
    return modelResponse;
}());
exports.modelResponse = modelResponse;

4. เตรียม Function ต่างๆ สำหรับเรียกใช้ในส่วน Function หลัก โดยแยกไว้เป็น Function ย่อยๆ ดังนี้
4.1 Function สำหรับ Recursive หา Parameter ตอน Request

function getDefinitionItemRequest(json ,lvCount, name,paramType) {    
    for (var item in json.definitions) {
        if (item === name) {
            var prop = json.definitions[item];
            for (var pName in prop.properties) {                
                var data = prop.properties[pName];
                if (data.$ref) {
                    
                    var rItem = prop.properties[pName];                    
                    pushDataToModelRequest(prop,pName,paramType,rItem.type,lvCount,rItem.description,rItem.required,rItem.example,rItem.enum);                    
                                        
                    var defineName = getDefineName(data.$ref);
                    getDefinitionItemRequest(json,(lvCount + 1),defineName,paramType);

                } else {
                    if (data.type == 'object' || data.type == 'array') {
                        // check has child                     
                        for (var cName in data) {
                            var child = data[cName];                                                
                            if (IsString(child)) {
                                if (child.indexOf('#/definitions') !== -1) {
                                    
                                    var defineName = getDefineName(data.$ref);
                                    getDefinitionItemRequest(json,(lvCount + 1),defineName,paramType);                                    
                                } 
                            } else {
                                for (var c2Name in child) {
                                    var child2 = child[c2Name];
                                    if (!IsString(child2)){
                                                                                
                                        var defineName = getDefineName(child2.$ref);                                        
                                        var rItem = child[c2Name];                                        
                                        pushDataToModelRequest(prop,defineName,paramType,rItem.type,lvCount,rItem.description,rItem.required,rItem.example,rItem.enum);
                                        getDefinitionItemRequest(json,(lvCount + 1),defineName,paramType);

                                    } else if (IsString(child2) && child2.indexOf('#' > -1)) {
                                                                                
                                        var defineName = getDefineName(child2);                                        
                                        var rItem = child[c2Name];                                        
                                        pushDataToModelRequest(prop,pName,paramType,rItem.type,lvCount,rItem.description,rItem.required,rItem.example,rItem.enum);
                                        getDefinitionItemRequest(json,(lvCount + 1),defineName,paramType); 
                                    }
                                }
                            }
                        }
                    } else {
                        var rItem = prop.properties[pName];                        
                        pushDataToModelRequest(prop,pName,paramType,rItem.type,lvCount,rItem.description,rItem.required,rItem.example,rItem.enum);
                    }
                }
            }

            return listDefineRequest;
        }
    }
}

4.2 Function สำหรับ Recursive หา Parameter ตอน Response

function getDefinitionItemResponse(json,lvCount,name,paramType) {    
    for (var item in json.definitions) {
        if (item === name) {
            var prop = json.definitions[item];            
            for (var pName in prop.properties) {
                
                var data = prop.properties[pName];
                
                if (data.$ref) {                    
                    var rItem = prop.properties[pName];                
                    pushDataToModelResponse(pName,paramType,rItem.type,lvCount,rItem.description,rItem.required,rItem.example);
                    var defineName = getDefineName(data.$ref);
                    getDefinitionItemResponse(json,(lvCount + 1),defineName,paramType);
                    
                } else {
                    if (data.type == 'object' || data.type == 'array') {
                        // check has child                     
                        for (var cName in data) {
                            var child = data[cName];                                                
                            if (IsString(child)) {
                                if (child.indexOf('#/definitions') !== -1) {
                                    var defineName =  getDefineName(data.$ref);
                                    getDefinitionItemResponse(json,(lvCount + 1),defineName,paramType);                                    
                                } 
                            } else {
                                for (var c2Name in child) {
                                    var child2 = child[c2Name];
                                    if (!IsString(child2)){
                                                                                
                                        var defineName = getDefineName(child2.$ref);                                        
                                        var rItem = child[c2Name];                  
                                        pushDataToModelResponse(defineName,paramType,rItem.type,lvCount,rItem.description,rItem.required,rItem.example);
                                        getDefinitionItemResponse(json,(lvCount + 1),defineName,paramType);    
                                                                            
                                    } else if (IsString(child2) && child2.indexOf('#' > -1)) {
                                                                                
                                        var defineName = getDefineName(child2);                                        
                                        var rItem = child[c2Name];                                        
                                        pushDataToModelResponse(pName,paramType,rItem.type,lvCount,rItem.description,rItem.required,rItem.example);
                                        getDefinitionItemResponse(json,(lvCount + 1),defineName,paramType); 
                                    }
                                }
                            }
                        }
                    } else {
                        var rItem = prop.properties[pName];
                        pushDataToModelResponse(pName,paramType,rItem.type,lvCount,rItem.description,rItem.required,rItem.example);
                    }
                }                
            }
            
            return listDefineResponse;
        }
    }
}

4.3 Function สำหรับตรวจสอบว่า Parameter ตัวนี้เป็น Require Filed หรือเปล่าตอน Request

function checkDefinitionRequestIsRequired(requestItem,requestName) {
    if (!requestItem.required) {
        return false;
    }
    for (var reqName in requestItem) {        
        var reqItem = requestItem.required[reqName];
        if (reqItem === requestName) {
            return true;
        } 
    }
    return false;
}

4.4 Function สำหรับตรวจสอบ Data type ว่าเป็น String หรือไม

function IsString(obj) {
    return obj !== undefined && obj != null && obj.toLowerCase !== undefined;
}

4.5 Function สำหรับหาชื่อ Define เพื่อ Recursive

function getDefineName(valueRefStr) {
    var ref = valueRefStr;
    var tmp = ref.split('/');
    var defineName = tmp[tmp.length - 1];
    return defineName;
}

4.6 Function สำหรับ Set ค่า Request สำหรับนำไปสร้าง Excel

function pushDataToModelRequest(jsonPopDefineList,paramName,paramType,dataType,paramLv,paramDesc,paramRequired,paramSimpleValue,paramPosible) {
    var requestModel = new modelService.modelRequest();
    requestModel.name = paramName;
    requestModel.paramType = paramType;
    requestModel.type = dataType;
    requestModel.lv = paramLv;
    if (paramDesc) {
        requestModel.description = paramDesc;
    } else {
        requestModel.description = '';
    }
    if (paramRequired) {
        requestModel.required = paramRequired;
    } else {
        requestModel.required = checkDefinitionRequestIsRequired(jsonPopDefineList,paramName);
    }                                    
    requestModel.simpleValue = paramSimpleValue;
    var posibleStr = '';
    if (paramPosible) {
        for (var eName in paramPosible) {
            posibleStr += paramPosible[eName] + ',';
        }
        if (posibleStr != '') {
            posibleStr = posibleStr.substr(0,posibleStr.length -1);
        }
    }
    requestModel.posibleValue = posibleStr;
    listDefineRequest.push(requestModel);
}

4.7 Function สำหรับ Set ค่า Response สำหรับนำไปสร้าง Excel

function pushDataToModelResponse(paramName,paramType,dataType,paramLv,paramDesc,paramRequired,paramSimpleValue) {
    var responseModel = new modelService.modelResponse();
    responseModel.name = paramName;
    responseModel.paramType = paramType;
    responseModel.type = dataType;
    responseModel.lv = paramLv;
    if (paramDesc) {
        responseModel.description = paramDesc;
    } else {
        responseModel.description = '';
    }
    if (paramRequired) {
        responseModel.required = paramRequired;
    }                                      
    responseModel.simpleValue = paramSimpleValue;
    listDefineResponse.push(responseModel);
}

5. สุดท้าย Function หลักที่ถูกเรียกหลังจาก Request Swagger แล้ว เพื่อเตรียม data และ generate excel โดยขอแบ่งเป็น 2 ส่วน
5.1 ส่วนทำหน้าที่ Prepare Data

for (var path in json.paths) {
        
    var pathItem = json.paths[path];

    for (var method in pathItem) {

        var itemMethod = pathItem[method];         
        // validate
        if (!itemMethod) continue;
        if (!itemMethod.summary) continue;
        if (!itemMethod.operationId) continue;

        var moduleName = '';
        if (itemMethod.tags) {
            moduleName = itemMethod.tags[0]; 
        }
        
        var serviceName = '';
        if (itemMethod.operationId) {
            serviceName = itemMethod.operationId.replace('/','');
        } else {
            if (itemMethod.summary) {
                serviceName = itemMethod.summary.replace('/','');
            }
        }            

        var serviceDesc = '';
        if (itemMethod.description) {
            serviceDesc = itemMethod.description;
        }
            
        // validate for some module or service you want to not generate
        if (skipModuleForGenerate.indexOf(moduleName) > -1) continue;
        if (skipServiceForGenerate.indexOf(serviceName) > -1) continue;

        var serviceModel = new modelService.modelService();
        console.log('module : ' + moduleName);
        serviceModel.module = moduleName;
        console.log('service : ' + serviceName);
        serviceModel.service = serviceName;
        serviceModel.description = serviceDesc;
        serviceModel.methodType = method;
        var param;
        var name;
        var paramType;
        var type;
        var desc;
        var required;
        /* Prepare Request */
        listDefineRequest = [];
        for (var paramName in itemMethod.parameters) {
            param = itemMethod.parameters[paramName];            
            name = param.name;            
            paramType = param.in;            
            type = param.type;            
            desc = param.description;            
            required = false;
            if (param.required) {
                required = true;
            }
            if (param.schema) {
                      
                if (param.schema.$ref) {                    
                    var define = getDefineName(param.schema.$ref);
                    var definition = getDefinitionItemRequest(json,1,define,paramType);
                    for (var i in definition) {
                        serviceModel.request.push(definition[i]);
                    }
                    listDefineRequest = [];

                } else if (param.schema.items.$ref) {                        
                    var define = getDefineName(param.schema.items.$ref);
                    var definition = getDefinitionItemRequest(json,1,define,paramType);
                    for (var i in definition) {
                        serviceModel.request.push(definition[i]);
                    }
                    listDefineRequest = [];

                } else {
                    var define = param.schema.description;
                    var definition = getDefinitionItemRequest(json,1,define,paramType);
                    for (var i in definition) {
                        serviceModel.request.push(definition[i]);
                    }
                    listDefineRequest = [];
                }
    
            } else {
                // LV 1            
                var requestModel = new modelService.modelRequest();
                requestModel.name = name;
                requestModel.paramType = paramType;
                requestModel.type = type;
                requestModel.lv = '1';
                requestModel.description = desc;
                requestModel.required = required;
                requestModel.simpleValue = '';
                serviceModel.request.push(requestModel);
            }        
        }
            
        /* Prepare Response */
        listDefineResponse = [];
        for (var paramName in itemMethod.responses) {
            // filter for response success
            if (paramName != 200) continue;

            param = itemMethod.responses[paramName];        
            name = param.name;            
            paramType = 'body';            
            type = param.type;            
            desc = param.description;            
            required = false;
            if (param.required) {
                required = true;
            }
            if (param.schema) {
                // LV x => for model response multi level
                if (param.schema.$ref) {                    
                    var define = getDefineName(param.schema.$ref);
                    var definition = getDefinitionItemResponse(json,1,define,paramType);
                    for (var i in definition) {
                        serviceModel.response.push(definition[i]);
                    }
                    listDefineResponse = [];
                } else if (param.schema.items.$ref) {

                    var define = getDefineName(param.schema.items.$ref);
                    var definition = getDefinitionItemResponse(json,1,define,paramType);
                    for (var i in definition) {
                        serviceModel.response.push(definition[i]);
                    }
                    listDefineResponse = [];
                } else {
                    
                    var define = param.schema.description;
                    var definition = getDefinitionItemResponse(json,1,define,paramType);
                    for (var i in definition) {
                        serviceModel.response.push(definition[i]);
                    }
                    listDefineResponse = [];
                }
    
            } else {
                // LV 1 => for model response single level
                var responseModel = new modelService.modelService();
                responseModel.name = name;
                responseModel.paramType = paramType;
                responseModel.type = type;
                responseModel.lv = '1';
                responseModel.description = desc;
                responseModel.required = required;
                responseModel.simpleValue = '';
                serviceModel.response.push(responseModel);
            }        
        }
    
        serviceList.push(serviceModel);
    }
}

5.2 ส่วนที่ทำหน้าสร้าง Excel ในส่วนนี้ขอแบ่งเป็น 3 ส่วน
5.2.1 ส่วนกำหนด Style ของ Cell

// 1. style for table header
var tHeadStyle = wb.createStyle({
    font: {
        color: '#FFFFFF',        
    },
    border: {
        left: {
            style: 'thin', //§18.18.3 ST_BorderStyle (Border Line Styles) ['none', 'thin', 'medium', 'dashed', 'dotted', 'thick', 'double', 'hair', 'mediumDashed', 'dashDot', 'mediumDashDot', 'dashDotDot', 'mediumDashDotDot', 'slantDashDot']
            color: '#000000' // HTML style hex value
        },
        right: {
            style: 'thin',
            color: '#000000'
        },
        top: {
            style: 'thin',
            color: '#000000'
        },
        bottom: {
            style: 'thin',
            color: '#000000'
        }
    },
    fill:{
        type: 'pattern', // Currently only 'pattern' is implemented. Non-implemented option is 'gradient'
        patternType: 'solid', //§18.18.55 ST_PatternType (Pattern Type)
        bgColor: '#000000', // HTML style hex value. defaults to black
        fgColor: '#000000' // HTML style hex value. defaults to black.
    }
});
// 2. style for table body cell with indent
var cellIndentStyle = wb.createStyle({
    alignment:{
        indent:1
    }    
});
// 3. style for table body cell
var cellBorderStyle = wb.createStyle({   
    border: { // §18.8.4 border (Border)
        left: {
            style: 'thin', //§18.18.3 ST_BorderStyle (Border Line Styles) ['none', 'thin', 'medium', 'dashed', 'dotted', 'thick', 'double', 'hair', 'mediumDashed', 'dashDot', 'mediumDashDot', 'dashDotDot', 'mediumDashDotDot', 'slantDashDot']
            color: '#000000' // HTML style hex value
        },
        right: {
            style: 'thin',
            color: '#000000'
        },
        top: {
            style: 'thin',
            color: '#000000'
        },
        bottom: {
            style: 'thin',
            color: '#000000'
        },
        diagonal: {
            style: 'thin',
            color: '#000000'
        },
        diagonalDown: true,
        diagonalUp: true,
        outline: true
    }
});

5.2.2 ส่วนสำหรับสร้าง Sheet Index

// start loop set table body of sheet index
for (let i = 0;i < index.length; i++) {     var obj = index[i];     ws.cell(row, 1).number(row -1);     ws.cell(row, 1).style(cellBorderStyle);     ws.cell(row, 2).string(obj.module);     ws.cell(row, 2).style(cellBorderStyle);     ws.cell(row, 3).string(obj.service);     ws.cell(row, 3).style(cellBorderStyle);     var serviceName = obj.service;     // for protect limit sheet name of excel     if (serviceName.length > 30) {
        serviceName = serviceName.substring(0,30);
    }

    // for link goto sheet service
    ws.cell(row, 4).formula('=HYPERLINK("#' + serviceName + '!A1","' + obj.service + '")');
    ws.cell(row, 4).style(cellBorderStyle);    
    row++;
}

5.2.3 ส่วนสำหรับสร้าง Sheet API Specification และ Save ออกมาเป็น Excel File

/* create excel specification follow api */
for (let service in serviceList) {
        
    console.log('module : ' + serviceList[service].module + ' / service : ' + serviceList[service].service);        
    var row = 1;
    var serviceName = serviceList[service].service;

    // for protect limit sheet name of excel
    if (serviceName.length > 30) {
        serviceName = serviceName.substring(0,30);
    }

    // create new sheet
    var ws = wb.addWorksheet(serviceName);
    
    ws.cell(row, 1,row,3,true).string('Module : ' + serviceList[service].module);

    // for back to index sheet
    ws.cell(row, 11).formula('=HYPERLINK("#index!A1","Back to index")');

    row++;
    ws.cell(row, 1,row,3,true).string('Service Name : ' + serviceList[service].service);
    row++;
    ws.cell(row, 1,row,3,true).string('Method Type : ' + serviceList[service].methodType);
    row++;
    ws.cell(row, 1,row,3,true).string('Description : ' + serviceList[service].description);
    row++;

    var count = 1;
    // set table header of request parameter
    ws.cell(row, 1).string('Request');
    row++;
    ws.cell(row, 1).string('Parameter');
    ws.cell(row,1).style(tHeadStyle);
    ws.cell(row, 2).string('Parameter Type');
    ws.cell(row,2).style(tHeadStyle);
    ws.cell(row, 3).string('Level');
    ws.cell(row,3).style(tHeadStyle);
    ws.cell(row, 4).string('Data Type');
    ws.cell(row,4).style(tHeadStyle);
    ws.cell(row, 5).string('Length');
    ws.cell(row,5).style(tHeadStyle);
    ws.cell(row, 6).string('O/M');
    ws.cell(row,6).style(tHeadStyle);
    ws.cell(row, 7).string('Description');
    ws.cell(row,7).style(tHeadStyle);
    ws.cell(row, 8).string('Simple Value');
    ws.cell(row,8).style(tHeadStyle);
    ws.cell(row, 9).string('Possible Value');
    ws.cell(row,9).style(tHeadStyle);
    ws.cell(row, 10).string('Formula/Remark');
    ws.cell(row,10).style(tHeadStyle); 
    row++;

    // start table body of request parameter
    for (let req in serviceList[service].request) {        
            
        var item = serviceList[service].request[req];
        
        // Column Parameter
        if (item.name) {
            ws.cell(row, 1).string(item.name);              
        } else {
            ws.cell(row, 1).string('');      
        }
        // check level of parameter for use style
        if (item.lv) {                            
            if (Number(item.lv) > 1){
                cellIndentStyle.alignment.indent = Number(item.lv) - 1;      
                ws.cell(row, 1).style(cellIndentStyle);                  
            }    
        }        
        ws.cell(row, 1).style(cellBorderStyle);     
        
        // Column Parameter Type (header , body)
        if (item.paramType) {
            ws.cell(row, 2).string(item.paramType);      
        } else {
            ws.cell(row, 2).string('');
        }     
        ws.cell(row, 2).style(cellBorderStyle);          
        
        // Column Parameter Level
        if (item.lv) {
            ws.cell(row, 3).number(Number(item.lv));               
        } else {
            ws.cell(row, 3).number(0);      
        } 
        ws.cell(row, 3).style(cellBorderStyle);           
        
        // Column Type (string , integer , ....)
        if (item.type) {
            ws.cell(row, 4).string(item.type);      
        } else {
            ws.cell(row, 4).string('');      
        }             
        ws.cell(row, 4).style(cellBorderStyle);     
        
        // Column Length for other condition or mapping db column size
        ws.cell(row, 5).style(cellBorderStyle);
        
        // Column O/M (optional , mandatory)
        if (item.required) {
            ws.cell(row, 6).string('M');
        } else {
            ws.cell(row, 6).string('O');
        }
        ws.cell(row, 6).style(cellBorderStyle);
        
        // Column Description
        if (item.description) {
            ws.cell(row, 7).string(item.description);      
        } else {
            ws.cell(row, 7).string('');      
        } 
        ws.cell(row, 7).style(cellBorderStyle); 
        
        // Column Simple value
        if (item.simpleValue) {
            ws.cell(row, 8).string(item.simpleValue.toString());
        } else {
            ws.cell(row, 8).string('');
        }   
        ws.cell(row, 8).style(cellBorderStyle);
        
        // Column Possible Value not include in swagger
        if (item.posibleValue) {
            ws.cell(row, 9).string(item.posibleValue);      
        } else {
            ws.cell(row, 9).string('');      
        } 
        ws.cell(row, 9).style(cellBorderStyle);    

        // Column Formula/Remark not include in swagger
        ws.cell(row, 10).style(cellBorderStyle);
        count ++;
        row++;
    }
    row++;

    // set table header of response parameter
    ws.cell(row, 1).string('Response');
    row++;
    ws.cell(row, 1).string('Parameter');
    ws.cell(row,1).style(tHeadStyle);
    ws.cell(row, 2).string('Parameter Type');
    ws.cell(row,2).style(tHeadStyle);
    ws.cell(row, 3).string('Level');
    ws.cell(row,3).style(tHeadStyle);
    ws.cell(row, 4).string('Data Type');
    ws.cell(row,4).style(tHeadStyle);
    ws.cell(row, 5).string('Length');
    ws.cell(row,5).style(tHeadStyle);
    ws.cell(row, 6).string('O/M');
    ws.cell(row,6).style(tHeadStyle);
    ws.cell(row, 7).string('Description');
    ws.cell(row,7).style(tHeadStyle);
    ws.cell(row, 8).string('Simple Value');
    ws.cell(row,8).style(tHeadStyle);
    ws.cell(row, 9).string('Possible Value');
    ws.cell(row,9).style(tHeadStyle);
    ws.cell(row, 10).string('Formula/Remark');
    ws.cell(row,10).style(tHeadStyle);
    row++;

    // start table body of response parameter
    for (let res in serviceList[service].response) {
                    
        var item = serviceList[service].response[res]; 
        
        // Column Parameter Name
        if (item.name) {
            ws.cell(row, 1).string(item.name);                       
        } else {
            ws.cell(row, 1).string('');      
        } 
        // check level of parameter for use style
        if (item.lv) {                              
            if (Number(item.lv) > 1){
                cellIndentStyle.alignment.indent = Number(item.lv) - 1;
                ws.cell(row, 1).style(cellIndentStyle);
            }                                              
        }      
        ws.cell(row, 1).style(cellBorderStyle); 
        
        // Column Parameter Type (header,body)
        if (item.paramType) {
            ws.cell(row, 2).string(item.paramType);      
        } else {
            ws.cell(row, 2).string('');
        }
        ws.cell(row, 2).style(cellBorderStyle);
        
        // Column Parameter Level
        if (item.lv) {
            ws.cell(row, 3).number(Number(item.lv));      
        } else {
            ws.cell(row, 3).number(0);      
        }    
        ws.cell(row, 3).style(cellBorderStyle);    
        
        // Column Type (string , integer , ....)
        if (item.type) {
            ws.cell(row, 4).string(item.type);      
        } else {
            ws.cell(row, 4).string('');      
        }     
        ws.cell(row, 4).style(cellBorderStyle);

        // Column Length for other condition or mapping db column size
        ws.cell(row, 5).style(cellBorderStyle);                  

        // Column O/M (optional , mandatory)
        if (item.required) {
            ws.cell(row, 6).string('M');
        } else {
            ws.cell(row, 6).string('O');
        }
        ws.cell(row, 6).style(cellBorderStyle);
        
        // Column Description
        if (item.description) {
            ws.cell(row, 7).string(item.description);      
        } else {
            ws.cell(row, 7).string('');      
        } 
        ws.cell(row, 7).style(cellBorderStyle);
        
        // Column Simple Value
        if (item.simpleValue) {
            ws.cell(row, 8).string(item.simpleValue.toString());
        } else {
            ws.cell(row, 8).string('');
        }        
        ws.cell(row, 8).style(cellBorderStyle);
        
        // Column Possible Value not include in swagger                          
        ws.cell(row, 9).style(cellBorderStyle);
        
        // Column Formula/Remark not include in swagger
        ws.cell(row, 10).style(cellBorderStyle);
        count ++;
        row++;
    }        
}

// save excel file
wb.write(excelFileName, function (err, stats) {
    if (err) {
        console.error(err);
    }  else {
        console.log(stats); // Prints out an instance of a node.js fs.Stats object
    }
});

คาดว่าบทความนี้จะช่วยบางท่านได้ไม่มากก็น้อย ขอบคุณครับ

Download ตัวอย่าง Output Excel ที่ได้จาก Output Excel
Download Source Code ทั้งหมดได้ที่ github

ใส่ความเห็น